home *** CD-ROM | disk | FTP | other *** search
/ MacWorld: Conference & Expo 2005 / Macworld Conference and Expo - Documentation CD-ROM (2004).iso / mac / Adobe Reader 6.0 / Adobe Reader 6.0.app / Contents / MacOS / JavaScripts / media.js < prev   
Text File  |  2004-01-30  |  66KB  |  2,381 lines

  1. // ADOBE SYSTEMS INCORPORATED
  2. // Copyright 2003 Adobe Systems Incorporated
  3. // All Rights Reserved
  4.  
  5. // NOTICE: Adobe permits you to use, modify, and distribute this file in
  6. // accordance with the terms of the Adobe license agreement accompanying it.  
  7. // If you have received this file from a source other than Adobe, then your use,
  8. // modification, or distribution of it requires the prior written permission of Adobe.
  9.  
  10. // media.js - Adobe Acrobat multimedia support
  11.  
  12. // The app and app.media properties and methods in this file are part of the public Acrobat
  13. // Multimedia API and may be used in your PDF JavaScript, EXCEPT where the name includes "priv"
  14. // or is otherwise noted as private. DO NOT USE any of these private properties or methods in
  15. // a PDF file, or YOUR PDF WILL BREAK in a future version of Acrobat.
  16.  
  17. // Greetings and thanks from the Acrobat Multimedia development team:
  18. // Dylan Ashe - Multimedia Framework and QuickTime
  19. // Michael Geary - JavaScript, Windows Media, Flash
  20. // Scott Grant - User Interface, Authoring, Sound, PDF File Access
  21. // Vivek Hebbar - Windows Built-in Player and Authoring
  22. // Liz McQuarrie - RealOne Player and Browsers
  23. // Ed Rowe - Project Lead and Mr. PDF
  24. // Jason Beique, Paul Herrin, Renato Maschion, Jason Reuer, Xintai Chang - QA and Developer Tech
  25.  
  26.  
  27. // Multimedia version number
  28.  
  29. console.println( 'Acrobat Multimedia Version 6.0' );
  30.  
  31. app.media.version = 6.0;
  32.  
  33.  
  34. // Set app.media.trace = true to enable method and event tracing in this code.
  35. // Note: app.media.trace is for testing only and will change in future versions.
  36.  
  37. app.media.trace = false;
  38.  
  39.  
  40. // The app.media.* constants below are passed back and forth between C++ and JavaScript code.
  41. // Always use these symbolic definitions instead of hard coded values, e.g.
  42. //   settings.windowType = app.media.windowType.floating;  /* NOT settings.windowType = 2; */
  43.  
  44. // PDF files may be opened under both newer and older versions of Acrobat.
  45. // Future versions of Acrobat may add new values to these lists. Your JavaScript code should
  46. // gracefully handle any values it encounters beyond those listed here.
  47. // Similarly, if you write JavaScript code for a future version of Acrobat, that code should
  48. // check app.media.version before it depends on new app.media.* constant values added in
  49. // that version. The lists below are marked to indicate which versions support which constants.
  50.  
  51.  
  52. // Values for settings.layout
  53.  
  54. app.media.layout =
  55. {
  56.     meet:        1,    // scale to fit all content, preserve aspect, no clipping, background fill
  57.     slice:        2,    // scale to fill window, preserve aspect, clip X or Y as needed
  58.     fill:        3,    // scale X and Y separately to fill window
  59.     scroll:        4,    // natural size with scrolling
  60.     hidden:        5,    // natural size with clipping
  61.     standard:    6    // use player's default settings
  62.     // End 6.0 values
  63. }
  64.  
  65.  
  66. // Values for settings.windowType
  67.  
  68. app.media.windowType =
  69. {
  70.     docked:        1,
  71.     floating:    2,
  72.     fullScreen:    3
  73.     // End 6.0 values
  74. }
  75.  
  76.  
  77. // Values for settings.monitorType
  78.  
  79. app.media.monitorType =
  80. {
  81.     document:        1,
  82.     nonDocument:    2,
  83.     primary:        3,
  84.     bestColor:        4,
  85.     largest:        5,
  86.     tallest:        6,
  87.     widest:            7
  88.     // End 6.0 values
  89. }
  90.  
  91.  
  92. // Values for settings.floating.align
  93.  
  94. app.media.align =
  95. {
  96.     topLeft:        1,
  97.     topCenter:        2,
  98.     topRight:        3,
  99.     centerLeft:        4,
  100.     center:            5,
  101.     centerRight:    6,
  102.     bottomLeft:        7,
  103.     bottomCenter:    8,
  104.     bottomRight:    9
  105.     // End 6.0 values
  106. }
  107.  
  108.  
  109. // Values for settings.floating.canResize
  110.  
  111. app.media.canResize =
  112. {
  113.     no:            1,
  114.     keepRatio:    2,
  115.     yes:        3
  116.     // End 6.0 values
  117. }
  118.  
  119.  
  120. // Values for settings.floating.over
  121.  
  122. app.media.over =
  123. {
  124.     pageWindow:    1,
  125.     appWindow:    2,
  126.     desktop:    3,
  127.     monitor:    4
  128.     // End 6.0 values
  129. }
  130.  
  131.  
  132. // Values for settings.floating.ifOffScreen
  133.  
  134. app.media.ifOffScreen =
  135. {
  136.     allow:            1,
  137.     forceOnScreen:    2,
  138.     cancel:            3
  139.     // End 6.0 values
  140. }
  141.  
  142.  
  143. // Default value for settings.visible
  144.  
  145. app.media.defaultVisible = true;
  146.  
  147.  
  148. // Values for rendition.type
  149.  
  150. app.media.renditionType =
  151. {
  152.     unknown:    0,        // rendition type not recognized by Acrobat
  153.     media:        1,
  154.     selector:    2
  155.     // End 6.0 values
  156. }
  157.  
  158.  
  159. // Values for event.media.code in Status event
  160.  
  161. app.media.status =
  162. {                        // event.media.text contains:
  163.     clear:         1,        // empty string - clears any message
  164.     message:     2,        // general message
  165.     contacting:     3,        // hostname being contacted
  166.     buffering:     4,        // percent complete
  167.     init:         5,        // name of the player being initialized
  168.     seeking:     6        // nothing
  169.     // End 6.0 values
  170. }
  171.  
  172.  
  173. // Values for event.media.closeReason in Close event
  174.  
  175. app.media.closeReason =
  176. {
  177.     general:     1,
  178.     error:         2,
  179.     done:         3,
  180.     stop:         4,
  181.     play:         5,
  182.     uiGeneral:     6,
  183.     uiScreen:     7,
  184.     uiEdit:         8,
  185.     docClose:     9,
  186.     docSave:    10,
  187.     docChange:    11
  188.     // End 6.0 values
  189. }
  190.  
  191.  
  192. // Values for player.open() return value
  193.  
  194. app.media.openCode =
  195. {
  196.     success:                    0,
  197.     failGeneral:                1,
  198.     failSecurityWindow:            2,
  199.     failPlayerMixed:            3,
  200.     failPlayerSecurityPrompt:    4,
  201.     failPlayerNotFound:            5,
  202.     failPlayerMimeType:            6,
  203.     failPlayerSecurity:            7,
  204.     failPlayerData:                8
  205.     // End 6.0 values
  206. }
  207.  
  208.  
  209. // Values for Error.raiseSystem
  210.  
  211. app.media.raiseSystem =
  212. {
  213.     fileError:    10
  214. }
  215.  
  216.  
  217. // Values for Error.raiseCode
  218.  
  219. app.media.raiseCode =
  220. {
  221.     fileNotFound:    17,
  222.     fileOpenFailed:    18
  223. }
  224.  
  225.  
  226. // In a PDF event, these event.name values indicate page-level actions.
  227.  
  228. app.media.pageEventNames =
  229. {
  230.     Open:        true,
  231.     Close:        true,
  232.     InView:        true,
  233.     OutView:    true
  234. }
  235.  
  236.  
  237. // Create and return a MediaPlayer without opening it.  Explicit values can be provided for all
  238. // the arguments listed below. If this function is called from a rendition action, it will get
  239. // the annot, rendition, and doc values from the event object if they are not explicitly provided.    
  240. // Returns player, or null on failure. Does not throw exceptions.
  241. // Any failures are reported to user, and result in null being returned. 
  242. // Events may be fired as a result of closing an existing player (see below).
  243. // Unless noStockEvents is true, stock event handlers are added to the returned player,
  244. // and will be added to the annot (if present) when player.open() is called later.
  245.  
  246. // player = app.media.createPlayer({
  247. //     doc: Doc, /* Required if both annot and rendition are omitted, e.g. for URL playback */
  248. //     annot: ScreenAnnot, /* Required for docked playback unless it is found in the Event object
  249. //       or settings.page is provided. The new player is associated with the annot. If a player
  250. //       was already associated with the annot, it is stopped and closed. */
  251. //     rendition: MediaRendition or RenditionList, /* Required unless rendition found in Event,
  252. //       or URL is present */
  253. //     URL: String, /* Either URL or rendition is required, with URL taking precedence */
  254. //     mimeType: string, /* Optional, ignored unless URL is present. If URL is present, either
  255. //       mimeType or settings.players, as returned by app.media.getPlayers(), is required */
  256. //     settings: MediaSettings, /* Optional, overrides the rendition settings */
  257. //     events: EventListener, /* Optional (if stock events are used, added after stock events) */
  258. //     noStockEvents: Boolean, /* Optional, default = false, use stock events or not */
  259. //     fromUser: Boolean, /* Optional, default depends on Event object */
  260. //     showAltText: Boolean, /* Optional, default = true */
  261. //     showEmptyAltText: Boolean /* Optional, default= ! fromUser */
  262. // });
  263.  
  264. app.media.createPlayer = function( args )
  265. {
  266.     try
  267.     {
  268.         return app.media.priv.createPlayer( app.media.argsDWIM( args ) );
  269.     }
  270.     catch( e )
  271.     {
  272.         app.media.alert( 'Exception', args, { error: e } );
  273.         return null;
  274.     }
  275. }
  276.  
  277.  
  278. // Create, open, and return a MediaPlayer. See app.media.createPlayer() for argument details
  279. // and other information.
  280. // This method fires several events which may include Open, Ready, Play and Focus. 
  281. // Returns player, or null on failure. Does not throw exceptions.
  282. // Any failures are reported to user.
  283.  
  284. app.media.openPlayer = function( args )
  285. {
  286.     var player = null;
  287.     try
  288.     {
  289.         // Do our own DWIM here to make sure args.doc is set in case of error
  290.         args = app.media.argsDWIM( args );    
  291.  
  292.         player = app.media.createPlayer( args );
  293.         if( player )
  294.         {
  295.             var result = player.open();        
  296.             if( result.code != app.media.openCode.success )    
  297.             {    
  298.                 player = null;    
  299.                 app.media.alert( 'Open', args, { code: result.code } );
  300.             }
  301.             else if( player.visible )
  302.                 player.setFocus();    // fires Focus event
  303.         }
  304.     }
  305.     catch( e )
  306.     {
  307.         player = null;
  308.         app.media.alert( 'Exception', args, { error: e } );
  309.     }
  310.  
  311.     return player;
  312. }
  313.  
  314.  
  315. // Open a new media player using the current event or explicit args as in app.media.createPlayer().
  316. // If an annot is provided or found in event object, and there is already a player open for that
  317. // annot, then start or resume playback on that player.
  318. // See app.media.openPlayer for argument details and other information.
  319. // Returns player, or null on failure. Does not throw exceptions.
  320. // Any failures are reported to user.
  321.  
  322. app.media.startPlayer = function( args )
  323. {
  324.     try
  325.     {
  326.         args = app.media.argsDWIM( args );    
  327.  
  328.         var player = args.annot && args.annot.player;
  329.         if( player && player.isOpen )
  330.             player.play();  // already opened, resume play
  331.         else
  332.             player = app.media.openPlayer( args );  // open a new player
  333.  
  334.         return player;
  335.     }
  336.     catch( e )
  337.     {
  338.         app.media.alert( 'Exception', args, { error: e } );
  339.         return null;
  340.     }
  341. },
  342.  
  343.  
  344. // app.media.Events constructor and prototype
  345.  
  346. // The Events constructor, events.add, and events.remove methods each takes any number of
  347. // arguments, where each argument can be either an event listener object or a previously
  348. // constructed app.media.Events object. The constructor and add() method add each listener
  349. // object to the Events object, and the remove() method removes listener objects.
  350.  
  351. // An event listener object is a collection of event methods and optional custom properties or
  352. // methods. Any method whose name matches /^on[A-Z]/ or /^after[A-Z]/ is an event method. Custom
  353. // methods and properties in an event listener should use names that do not match these case
  354. // sensitive patterns.
  355.  
  356. // When an event listener method is called, 'this' is the event listener object. The event
  357. // listener can have custom properties like any other object. If an event listener is nested
  358. // inside another function (such as a constructor), then the event methods can also directly
  359. // access any variables defined in the parent function, even when the event method is called
  360. // after the parent function returns.
  361.  
  362. // The same event listener object may be added into more than one Events object. The object is
  363. // not copied; each Events object has a reference to the original event listener object, so any
  364. // properties of the listener object are shared by every Events object it is added to.
  365.  
  366. // Implementation note:
  367. // An app.media.Events object has a listeners property which is a object containing
  368. // onVariousEvent and afterVariousEvent properties. Each of these properties is an array of
  369. // references to event listener objects which contain the event methods.
  370. // So, for example, after this call:
  371. //   var events = new app.media.Events({ onPlay: function(e){} });
  372. // events.listeners.onPlay[0] is a reference to a new { onPlay: function(e){} } object, and
  373. // events.listeners.onPlay[0].onPlay is a reference to the onPlay method.
  374.  
  375. app.media.Events = function()
  376. {
  377.     this.listeners = {};            // start with empty listeners object
  378.     this.dispatching = 0;            // not currently dispatching any events
  379.     this.removed = {};                // listener names that need delayed removal
  380.     this.privAddRemove( arguments, this.privAdd );  // add any event listener object arguments
  381. }
  382.  
  383.  
  384. app.media.Events.prototype =
  385. {
  386.  
  387.  
  388. // Add any number of event listener objects or other app.media.Events objects.
  389. // events.add() may be called inside an event listener method, and any new listeners that are
  390. // added for the current event will be called for that same event.
  391. // If the same listener object is added twice, the second add is ignored.
  392. // Listeners for a given event (e.g. onClose) are called in the order in which they were added.
  393.  
  394. // events.add( event listener or app.media.Events object(s) )
  395.  
  396. add: function()
  397. {
  398.     this.privAddRemove( arguments, this.privAdd );
  399. },
  400.  
  401.  
  402. // Remove any number of event listener objects or other app.media.Events objects.
  403. // events.remove() may be called inside an event listener method, to remove the current listener
  404. // or any other.
  405.  
  406. // events.remove( event listener or app.media.Events object(s) )
  407.  
  408. remove: function()
  409. {
  410.     this.privAddRemove( arguments, this.privRemove );
  411. },
  412.  
  413.  
  414. // Private method for events.add() and events.remove().
  415. // Loop through all the listener methods in each argument and call doAddRemove for every one.
  416.  
  417. privAddRemove: function( args, doAddRemove )
  418. {
  419.     for( var i = 0;  i < args.length;  i++ )  // for each event listener object argument in passed array
  420.     {
  421.         var events = args[i];
  422.         if( events.listeners )
  423.         {
  424.             // It's an app.media.Events object, add or remove every listener in each array
  425.             for( var name in events.listeners )
  426.             {
  427.                 var array = events.listeners[name];
  428.                 for( var i = 0;  i < array.length;  i++ )
  429.                 {
  430.                     doAddRemove.call( this, array[i], name );
  431.                 }
  432.             }
  433.         }
  434.         else
  435.         {
  436.             // It's an event listener object, add or remove each method
  437.             for( var name in events )
  438.             {
  439.                 // Only interested in onFoo and afterFoo methods, not custom properties
  440.                 if( name.search(/^on[A-Z]/) == 0  ||  name.search(/^after[A-Z]/) == 0 )
  441.                 {
  442.                     doAddRemove.call( this, events, name );
  443.                 }
  444.             }
  445.         }
  446.     }
  447.  
  448.     this.privSetDispatch();  // Add or remove the dispatch() method as needed
  449. },
  450.  
  451.  
  452. // Private method for events.add().
  453. // Adds a reference to a listener object into events.listeners[name]
  454. // Does nothing if listener already added.
  455.  
  456. privAdd: function( listener, name )
  457. {
  458.     if( typeof(listener) != "object"  ||  typeof(listener[name]) != "function" )
  459.         return;  // not a valid object and method
  460.  
  461.     var array = this.listeners[name];  // get our existing event listener array
  462.     if( ! array )
  463.     {
  464.         this.listeners[name] = [ listener ];  // no array yet, add array with one listener object
  465.     }
  466.     else  // we have a listener array, append listener to it if it's not already present
  467.     {
  468.         for( var i = 0;  i < array.length;  i++ )
  469.         {
  470.             if( array[i] === listener )
  471.                 return;  // already present, don't add another
  472.         }
  473.  
  474.             array[i] = listener;  // append listener to array
  475.         }
  476. },
  477.  
  478.  
  479. // Private method for events.remove().
  480. // Removes a listener object reference from events.listeners[name]
  481.  
  482. privRemove: function( listener, name )
  483. {
  484.     var array = this.listeners[name];  // existing event listener array
  485.     if( ! array )
  486.         return;  // no listeners with this name
  487.  
  488.     for( var i = 0;  i < array.length;  i++ )  // Look for the listener object in the array
  489.     {
  490.         if( array[i] === listener )
  491.         {
  492.             // Found the listener in the array, decide what to do with it
  493.             if( this.dispatching )  // Can't remove while dispatching, mark for later removal
  494.                 array[i] = null,  this.removed[name] = this.needCleanup = true;
  495.             else if( array.length > 1 )
  496.                 array.splice( i, 1 );  // Remove listener from array
  497.             else
  498.                 delete this.listeners[name];  // Last one, remove array entirely
  499.  
  500.             return;  // Listener is already in the array
  501.         }
  502.     }
  503. },
  504.  
  505.  
  506. // Private function for events.add(), events.remove() and events.privCleanup().
  507. // Sets or deletes the dispatch method depending on whether there are any event listeners.
  508.  
  509. privSetDispatch: function()
  510. {
  511.     for( var name in this.listeners )  // are there any listeners?
  512.     {
  513.         this.dispatch = this.privDispatch;  // found a listener, set the dispatch method
  514.         return;
  515.     }
  516.  
  517.     delete this.dispatch;  // no listeners, remove the dispatch method
  518. },
  519.  
  520.  
  521. // events.privDispatch() is a private method that contains the code for events.dispatch().
  522. // To dispatch an event, C++ code calls events.dispatch(), only if that method exists.
  523. // The rest of the event dispatching machinery is implemented in JavaScript.
  524. // We turn event dispatching on and off dynamically by setting and removing the events.dispatch
  525. // property, which is a reference to events.privDispatch().
  526. // If you call events.dispatch() directly from JavaScript, event.target.doc or event.media.doc
  527. // must match the current document.
  528. // You can implement your own event dispatcher from scratch by providing an events object
  529. // with a dispatch method that takes an event argument as this method does.
  530. // This function is reentrant.
  531.  
  532. privDispatch: function( event )
  533. {
  534.     if( !event.media )
  535.         event.media = {};
  536.  
  537.     // PDF events may have spaces in their names, so make a copy of event.name with spaces removed
  538.     event.media.id = event.name.replace( / /, '' );
  539.  
  540.     // Use doc and events properties in either event.target or in the event object itself
  541.     if( event.target )
  542.     {
  543.         event.media.doc = event.target.doc;
  544.         event.media.events = event.target.events;
  545.     }
  546.  
  547.     ++this.dispatching;  // if this.dispatching > 0, events.remove() will use deferred removal
  548.  
  549.     try
  550.     {
  551.         // First call immediate (onFoo) listener methods
  552.         this.privDispatchNow( 'on', event );  // may reenter this function
  553.  
  554.         // Turn stopDispatch off in case an immediate listener turned it on
  555.         delete event.stopDispatch;
  556.  
  557.         // If there are any deferred (afterFoo) listeners, post event to queue,
  558.         // but don't bother if an immediate listener stopped all dispatching
  559.         if( ! event.stopAllDispatch )
  560.             if( this.listeners[ 'afterEveryEvent' ]  ||  this.listeners[ 'after' + event.media.id ] )
  561.                 app.media.priv.postEvent( event );
  562.     }
  563.     catch( e )
  564.     {
  565.         app.media.priv.trace( 'di throw: ' + e.message );
  566.     }
  567.  
  568.     --this.dispatching;
  569.  
  570.     // If any event listeners were marked for removal while we were dispatching events,
  571.     // and we are done with any nested dispatch calls, then clean up the listener arrays.
  572.     if( this.needCleanup  &&  ! this.dispatching )
  573.         this.privCleanup();
  574. },
  575.  
  576.  
  577. // Private method for events.dispatch().
  578. // Clean up any event listener arrays that have had entries marked for removal.
  579. // Each event name that needs to be cleaned up has an entry in this.removed.
  580. // Each listener that is to be removed has been set to null.
  581.  
  582. privCleanup: function()
  583. {
  584.     for( var name in this.removed )
  585.     {
  586.         var array = this.listeners[name];
  587.         for( var i = 0;  i < array.length;  i++ )
  588.         {
  589.             if( ! array[i] )
  590.                 array.splice( i--, 1 );  // Remove listener from array and back up index
  591.         }
  592.  
  593.         if( array.length == 0 )
  594.             delete this.listeners[name];  // Remove listener array if it's now empty
  595.     }
  596.  
  597.     this.removed = {};
  598.  
  599.     this.privSetDispatch();  // Remove the dispatch method if there are no more listeners
  600.  
  601.     delete this.needCleanup;
  602. },
  603.  
  604.  
  605. // Private method for events.dispatch().
  606. // Immediately dispatch a single event to all listeners for that event.
  607. // prefix is 'on' or 'after', and event is the event object.
  608. // Calls both EveryEvent listener methods and any specific listener methods for the event.
  609. // This function is reentrant.
  610.  
  611. privDispatchNow: function( prefix, event )
  612. {
  613.     this.privCallMethods( event, prefix + 'EveryEvent' );  // may reenter this function
  614.     this.privCallMethods( event, prefix + event.media.id );  // may reenter this function
  615. },
  616.  
  617.  
  618. // Private method for events.dispatch().
  619. // Loop through the events.listeners[name] array and call each event listener method found
  620. // there, with 'this' as the event listener object that contains the method.
  621. // If new listeners are added while dispatching, they will also be called.
  622. // This function is reentrant.
  623.  
  624. privCallMethods: function( event, name )
  625. {
  626.     var array = this.listeners[name];
  627.     if( array )
  628.     {
  629.         // Call each listener method in the array
  630.         for( var i = 0;  i < array.length;  i++ )
  631.         {
  632.             if( event.stopDispatch || event.stopAllDispatch )
  633.                 break;
  634.  
  635.             var listener = array[i];
  636.             if( listener )    // listener is null if removed while dispatching
  637.             {
  638.                 listener[name]( event );  // may reenter this function
  639.             }
  640.         }
  641.     }
  642. },
  643.  
  644.  
  645. }
  646. // end app.media.Events.prototype
  647.  
  648.  
  649. // A simple event queue.
  650.  
  651. // app.media.priv.postEvent(event) and app.media.priv.dispatchQueuedEvents() use
  652. // doc.media.priv.queue to manage a per-doc event queue.
  653. // This private method has the same restrictions on calling it as app.media.Events.privDispatch().
  654.  
  655. app.media.priv.postEvent = function( event )
  656. {
  657.     var q = event.media.doc.media.priv.queue;
  658.     if( ! q )
  659.         q = event.media.doc.media.priv.queue = {};
  660.  
  661.     if( ! q.list )
  662.         q.list = [];
  663.  
  664.     q.list.push( event );
  665.  
  666.     if( ! q.timer )
  667.     {
  668.         q.timer = app.setTimeOut( 'app.media.priv.dispatchPostedEvents(this);', 1, false ); // no disp while modal dlg up
  669.         q.timer.media = { doc: event.media.doc };  // allow access to doc from timer obj
  670.     }
  671. }
  672.  
  673.  
  674. // Called from the short timer set by app.media.priv.postEvent() to dispatch all posted events.
  675.  
  676. app.media.priv.dispatchPostedEvents = function( doc )
  677. {
  678.     try
  679.     {
  680.         // If doc already closed, bail! Closing doc does NOT unregister timeouts!
  681.         // They may or may not fire depending on whether they are GCed before getting fired.
  682.         // Event.target is our timeout obj.
  683.         if ( event.target.media.doc.closed )
  684.             return;    
  685.         
  686.         // Grab and delete queue--any new event queued while dispatching will be dispatched later
  687.         var q = doc.media.priv.queue;
  688.         var list = q.list;
  689.         delete q.list;
  690.         delete q.timer;
  691.  
  692.         for( var i = 0;  i < list.length;  i++ )
  693.         {
  694.             // Stop dispatching "after" events if the doc is closed, checked here in case an
  695.             // event method closes the doc. Do not check in privCallMethods--an event method
  696.             // that closes the doc should set event.stopDispatchAll.
  697.             if( doc.closed )
  698.                 return;  
  699.  
  700.             var e = list[i];
  701.             if( e.media.events )
  702.                 e.media.events.privDispatchNow( "after", e );
  703.         }
  704.     }
  705.     catch( e )
  706.     {
  707.         app.media.priv.trace( 'dpe throw: ' + e.message );
  708.     }
  709. }
  710.  
  711.  
  712. // app.media.Markers constructor and prototype
  713.  
  714. app.media.Markers = function( player )
  715. {
  716.     this.player = player;
  717. }
  718.  
  719.  
  720. app.media.Markers.prototype =
  721. {
  722.  
  723. // Finds a marker by name, index number, time, or frame.
  724. // Index numbers are not in any guaranteed order.
  725. // If a time or frame is given, returns the nearest marker at or before that location.
  726. // Returns null if no matching marker is found.
  727. //
  728. // marker = markers.get( cName );
  729. // marker = markers.get({ name: cName });
  730. // marker = markers.get({ index: nIndex });
  731. // marker = markers.get({ time: nSeconds });
  732. // marker = markers.get({ frame: nFrame });
  733.  
  734. get: function( m )
  735. {
  736.     if( ! this.privByIndex )
  737.         this.player.privLoadMarkers();
  738.  
  739.     var retMarker = null;
  740.  
  741.     if( this.privByIndex.length > 0 )
  742.     {
  743.         switch( typeof(m) )
  744.         {
  745.             case 'string':
  746.                 retMarker = this.privByName[m];
  747.                 break;
  748.     
  749.             case 'object':
  750.                 retMarker = (
  751.                     m.name  !== undefined ? this.privByName[ m.name ] :
  752.                     m.index !== undefined ? this.privByIndex[ m.index ] :
  753.                     m.time  !== undefined ? this.privFind( 'time',  m.time  ) :
  754.                     m.frame !== undefined ? this.privFind( 'frame', m.frame ) :
  755.                     undefined );
  756.                 break;
  757.         }
  758.     }
  759.  
  760.     if( retMarker === undefined )
  761.         retMarker = null;
  762.  
  763.     return retMarker;
  764. },
  765.  
  766.  
  767. // Private method for markers.get() to find a marker by time or frame.
  768.  
  769. privFind: function( prop, value )
  770. {
  771.     if( value < 0 )
  772.         return;  // negative time or frame not allowed
  773.  
  774.     var array = this.privByIndex;
  775.     var length = array.length;
  776.  
  777.     // Search for nearest marker <= passed value; does not assume any sort order.
  778.     var nearIdx;
  779.     var nearDist = Infinity;
  780.     for( var i = 0;  i < length;  i++ )
  781.     {
  782.         // Test for undefined in case some markers have time and some have frame
  783.         var v = array[i][prop];
  784.         if( v !== undefined )
  785.         {
  786.             var dist = ( value - v );
  787.             if( dist >= 0  &&  dist < nearDist )
  788.             {
  789.                 // have a new "nearest marker <= value"
  790.                 nearIdx = i;
  791.                 nearDist = dist;        
  792.             }
  793.         }
  794.     }
  795.  
  796.     if( nearIdx !== undefined )
  797.         return array[ nearIdx ];
  798. },
  799.  
  800.  
  801. }
  802. // end app.media.Markers.prototype
  803.  
  804.  
  805. // app.Monitors constructor and prototype
  806.  
  807. app.Monitors = function()
  808. {
  809.     this.length = 0;
  810. }
  811.  
  812.  
  813. app.Monitors.prototype =
  814. {
  815.  
  816.  
  817. // monitors.clear()
  818.  
  819. clear: function()
  820. {
  821.     while( this.length > 0 )
  822.         delete this[ --this.length ];
  823. },
  824.  
  825.  
  826. // monitors.push( value )
  827. // Appends a reference to a monitor object to the array.
  828.  
  829. push: function( value )
  830. {
  831.     this[ this.length++ ] = value;
  832. },
  833.  
  834.  
  835. // monitors = monitors.select( monitorType, doc )
  836. // Filter a Monitors array based on an app.media.monitorType value as used in PDF.
  837. // doc is required if monitorType is app.media.monitorType.document or
  838. // app.media.monitorType.nonDocument, otherwise it is ignored.
  839. // Returns new array of references to the selected monitor objects.
  840.  
  841. select: function( monitorType, doc )
  842. {
  843.     switch( monitorType )
  844.     {
  845.         default:
  846.         case app.media.monitorType.document:    return this.document(doc).primary();
  847.         case app.media.monitorType.nonDocument:    return this.nonDocument(doc).primary();
  848.         case app.media.monitorType.primary:        return this.primary();
  849.         case app.media.monitorType.bestColor:    return this.bestColor().primary();
  850.         case app.media.monitorType.largest:        return this.largest().primary();
  851.         case app.media.monitorType.tallest:        return this.tallest().primary();
  852.         case app.media.monitorType.widest:        return this.widest().primary();
  853.     }
  854. },
  855.  
  856.  
  857. // monitors.filter( ranker, minRank )
  858. // Returns a Monitors array containing the monitors that score the highest rank according to
  859. // the ranker function. The ranker function takes a Monitor parameter and returns a numeric or
  860. // boolean rank for it (or any type that can be converted to a number).
  861. // A numeric rank may be any finite value.
  862. // If minRank is not specified, the array returned always contains at least one element (unless
  863. // the original array was already empty).
  864. // If minRank is specified but the final rank is less, the array returned is empty.
  865. // If multiple monitors tie for the highest rank, the returned array contains those monitors in
  866. // the same order as the original array.
  867.  
  868. filter: function( ranker, minRank )
  869. {
  870.     var r = new app.Monitors;
  871.     var rank = ( minRank != undefined ? minRank : -Infinity );
  872.  
  873.     for( var i = 0;  i < this.length;  i++ )
  874.     {
  875.         // Rank the next Monitor object
  876.         var m = this[i];
  877.         var mRank = ranker( m );
  878.  
  879.         // If it outranks the best previous ranking, clear the result list.
  880.         // If it's the same rank, add it to the result list.
  881.         if( mRank >= rank )
  882.         {
  883.             if( mRank > rank )
  884.                 r.clear();  // new outranks old, clear result array
  885.  
  886.             r.push( m );  // append new result to any same-ranked results
  887.             rank = mRank;  // save new rank
  888.         }
  889.     }
  890.  
  891.     return r;
  892. },
  893.  
  894.  
  895. // monitors.bestColor( minColor )
  896. // Returns a Monitors array containing the monitor(s) that have the greatest color depth.
  897. // Returns empty array if minColor is specified and no monitor in the array has a color depth of
  898. // at least minColor bits.
  899.  
  900. bestColor: function( minColor )
  901. {
  902.     return this.filter(
  903.         function( m ) { return m.colorDepth; },
  904.         minColor );
  905. },
  906.  
  907.  
  908. // monitors.bestFit( width, height, bRequire )
  909. // Returns a Monitors array containing the monitor(s) that have at least the specified width and
  910. // height with the least amount of excess area. If all monitors are smaller than the specified
  911. // width and height, then returns an empty array if bRequire is true, or an array of the largest
  912. // available monitors if bRequire is false.
  913.  
  914. bestFit: function( width, height, bRequire )
  915. {
  916.     var tiny = -1000000000;
  917.     var area = ( width * height );
  918.  
  919.     return this.filter(
  920.         function( m )
  921.         {
  922.             var mWidth  = m.rect[2] - m.rect[0];
  923.             var mHeight = m.rect[3] - m.rect[1];
  924.  
  925.             // Rank lowest if it doesn't fit at all, else rank by least excess area
  926.             return(
  927.                 width > mWidth  ||  height > mHeight  ?  tiny  :
  928.                 area - ( mWidth * mHeight ) );
  929.         },
  930.         bRequire ? ( tiny + 1 ) : tiny );
  931. },
  932.  
  933.  
  934. // monitors.desktop()
  935. // Returns a Monitors array with a single Monitor that represents the entire virtual desktop:
  936. //    rect = the union of all Monitor.rect values
  937. //    workRect = the union of all the workRect values (may include parts of monitors that are 
  938. //               outside their workRects).
  939. //    colorDepth = the least color depth of any monitor
  940. //    isPrimary = (not present)
  941.  
  942. desktop: function()
  943. {
  944.     if( ! this.length )
  945.         return [];
  946.  
  947.     var r = { rect: [0,0,0,0], workRect: [0,0,0,0], colorDepth: Number.MAX_VALUE };
  948.  
  949.     for( var i = 0;  i < this.length;  i++ )
  950.     {
  951.         var m = this[i];
  952.  
  953.         r.rect = app.media.priv.rectUnion( r.rect, m.rect );
  954.         r.workRect = app.media.priv.rectUnion( r.workRect, m.workRect );
  955.         r.colorDepth = Math.min( r.colorDepth, m.colorDepth );
  956.     }
  957.  
  958.     var result = new app.Monitors;
  959.     result.push( r );
  960.  
  961.     return result;
  962. },
  963.  
  964.  
  965. // monitors.document( doc, bRequire )
  966. // Returns a Monitors array containing the monitor(s) that display the greatest amount of the
  967. // specified document.
  968. // If bRequire is true, returns empty array if the document does not appear on any monitor.
  969. // If bRequire is false and document does not appear on any monitor, returns array containing
  970. // all monitors.
  971.  
  972. document: function( doc, bRequire )
  973. {
  974.     return this.mostOverlap( doc.outerDocWindowRect, bRequire ? 1 : undefined );
  975. },
  976.  
  977.  
  978. // monitors.largest( minArea )
  979. // Returns a Monitors array containing the monitor(s) with the greatest area.
  980. // Returns empty array if minArea is specified and the greatest area is less than that.
  981.  
  982. largest: function( minArea )
  983. {
  984.     return this.filter(
  985.         function( m ) { return app.media.priv.rectArea( m.rect ); },
  986.         minArea );
  987. },
  988.  
  989.  
  990. // monitors.leastOverlap( rect, maxOverlapArea )
  991. // Returns a Monitors array containing the monitor(s) which have the least area overlapping rect.
  992. // Returns empty array if maxOverlapArea is specified and all monitors overlap rect by a greater
  993. // amount.
  994.  
  995. leastOverlap: function( rect, maxOverlapArea )
  996. {
  997.     if( maxOverlapArea !== undefined )  // if undefined must stay undefined (-undefined is NAN)
  998.         maxOverlapArea = -maxOverlapArea;
  999.  
  1000.     return this.filter(
  1001.         function( m ) { return -app.media.priv.rectIntersectArea( m.rect, rect ); }, maxOverlapArea );
  1002. },
  1003.  
  1004.  
  1005. // monitors.mostOverlap( rect, minOverlapArea )
  1006. // Returns a Monitors array containing the monitor(s) which have the most area overlapping rect.
  1007. // Returns empty array if minOverlapArea is specified and there is no monitor with at least that
  1008. // much overlap.
  1009.  
  1010. mostOverlap: function( rect, minOverlapArea )
  1011. {
  1012.     return this.filter(
  1013.         function( m ) { return app.media.priv.rectIntersectArea( m.rect, rect ); },
  1014.         minOverlapArea );
  1015. },
  1016.  
  1017.  
  1018. // monitors.nonDocument( doc, bRequire )
  1019. // Returns a Monitors array containing the monitor(s) that display none of, or the least amount
  1020. // of the specified document.
  1021. // If parts of the document appear on every monitor, then returns empty array if bRequire is true
  1022. // or a copy of the original monitors array if bRequire is false.
  1023.  
  1024. nonDocument: function( doc, bRequire )
  1025. {
  1026.     return this.leastOverlap( doc.outerDocWindowRect, bRequire ? 0 : undefined );
  1027. },
  1028.  
  1029.  
  1030. // monitors.primary()
  1031. // Returns a Monitors array containing at most one entry, the primary monitor.
  1032.  
  1033. primary: function()
  1034. {
  1035.     return this.filter(
  1036.         function( m ) { return m.isPrimary; },
  1037.         1 );
  1038. },
  1039.  
  1040.  
  1041. // monitors.secondary()
  1042. // Returns a Monitors array containing all secondary (non-primary) monitors.
  1043.  
  1044. secondary: function()
  1045. {
  1046.     return this.filter(
  1047.         function( m ) { return ! m.isPrimary; },
  1048.         1 );
  1049. },
  1050.  
  1051.  
  1052. // monitors.tallest( minHeight )
  1053. // Returns a Monitors array containing the monitor(s) with the greatest height.
  1054.  
  1055. tallest: function( minHeight )
  1056. {
  1057.     return this.filter(
  1058.         function( m ) { return m.rect[3] - m.rect[1]; },
  1059.         minHeight );
  1060. },
  1061.  
  1062.  
  1063. // monitors.widest( minWidth )
  1064. // Returns a Monitors array containing the monitor(s) with the greatest width.
  1065.  
  1066. widest: function( minWidth )
  1067. {
  1068.     return this.filter(
  1069.         function( m ) { return m.rect[2] - m.rect[0]; },
  1070.         minWidth );
  1071. },
  1072.  
  1073.  
  1074. }
  1075. // end app.Monitors.prototype
  1076.  
  1077.  
  1078. // app.media.Players constructor and prototype
  1079.  
  1080. app.media.Players = function()
  1081. {
  1082.     this.length = 0;
  1083. }
  1084.  
  1085.  
  1086. app.media.Players.prototype =
  1087. {
  1088.  
  1089.  
  1090. // Players.clear()
  1091.  
  1092. clear: function()
  1093. {
  1094.     while( this.length > 0 )
  1095.         delete this[ --this.length ];
  1096. },
  1097.  
  1098.  
  1099. // Players.push( value )
  1100. // Appends a reference to a Player object to the array.
  1101.  
  1102. push: function( value )
  1103. {
  1104.     this[ this.length++ ] = value;
  1105. },
  1106.  
  1107.  
  1108. // players = Players.select( args )
  1109. // Filters a Players array based on any of the PlayerInfo properties.
  1110. // The array is not in any particular order.
  1111. // The object argument lists the properties to filter on.
  1112. // String properties (id, name, version) can use either strings or regular expressions.
  1113. // Example: get all players with 'QuickTime' in the id:
  1114. //    var p = app.media.getPlayers().select({ id: /QuickTime/ });
  1115. // All specified properties must be present and must match exactly (or must pass regex match).
  1116. // If no properties are specified, all Players in the array will be present in the returned array.
  1117. // If no players in the array match the search criteria, returns an empty Players array.
  1118.  
  1119. select: function( args )
  1120. {
  1121.     var r = new app.media.Players;
  1122.  
  1123.     for( var i = 0;  i < this.length;  i++ )
  1124.     {
  1125.         var info = this[i];  // Get the PlayerInfo object
  1126.         var ok = true;
  1127.  
  1128.         for( var prop in args )  // check each property that the caller passed in
  1129.         {
  1130.             if( !( prop in info ) )
  1131.                 return [];  // unknown selection property, probably future PDF version, give up
  1132.  
  1133.             // Handle either a regular expression or a string, number, or boolean comparison
  1134.             if( args[prop].exec ? args[prop].exec(info[prop]) == null : args[prop] != info[prop] )
  1135.             {
  1136.                 ok = false;
  1137.                 break;
  1138.             }
  1139.         }
  1140.  
  1141.         if( ok )
  1142.             r.push( info );  // passed all tests, append reference to PlayerInfo
  1143.     }
  1144.  
  1145.     return r;
  1146. },
  1147.  
  1148.  
  1149. }
  1150. // end app.media.Players.prototype
  1151.  
  1152.  
  1153. // app.media.MediaPlayer constructor and prototype
  1154.  
  1155. app.media.MediaPlayer = function()
  1156. {
  1157. }
  1158.  
  1159.  
  1160. app.media.MediaPlayer.prototype =
  1161. {
  1162.  
  1163.  
  1164. // MediaPlayer.open()
  1165.  
  1166. open: function()
  1167. {
  1168.     var ret;
  1169.  
  1170.     try
  1171.     {
  1172.         // Add stock annot events and cross-references only if we open the movie
  1173.         if( this.annot ) 
  1174.         {
  1175.             app.media.priv.AddStockEventsHelper( this.annot, app.media.getAnnotStockEvents( this.settings.windowType ) );
  1176.             this.annot.player = this;
  1177.         }
  1178.  
  1179.         ret = this.privOpen.apply( this, arguments );
  1180.         if( ret.code != app.media.openCode.success )
  1181.             app.media.removeStockEvents( this );
  1182.     }
  1183.     catch( e ) 
  1184.     {
  1185.         app.media.removeStockEvents( this );
  1186.         throw e;
  1187.     }
  1188.     
  1189.     return ret;
  1190. },
  1191.  
  1192.  
  1193. }
  1194. // end app.media.MediaPlayer.prototype
  1195.  
  1196.  
  1197. // Determine whether any media playback is allowed and return true if it is.
  1198. // If playback is not allowed, then alert the user and return false.
  1199.  
  1200. // bCanPlay = app.media.canPlayOrAlert({ doc: Doc });
  1201.  
  1202. app.media.canPlayOrAlert = function( args )
  1203. {
  1204.     var canPlay = args.doc.media.canPlay;
  1205.     if( canPlay.yes )
  1206.         return true;  // Playback is allowed
  1207.  
  1208.     app.media.alert( 'CannotPlay', args, { canPlayResult: canPlay } );
  1209.  
  1210.     return false;
  1211. }
  1212.  
  1213.  
  1214. // Return a settings object to play a rendition, if all playback requirements are met.
  1215. // Otherwise return settings to "play" alt text, if showAltText and showEmptyAltText allow and
  1216. // alt text is available, or else return null.
  1217.  
  1218. // settings = app.media.getRenditionSettings({
  1219. //       doc: Doc,
  1220. //     settings: MediaSettings, /* Optional, shallow-copied into returned settings */
  1221. //     rendition: MediaRendition or RenditionList,
  1222. //     showAltText: Boolean, /* Optional, default = false */
  1223. //     showEmptyAltText: boolean, /* Optional, default = false */
  1224. //     fromUser: boolean /* Optional, default = false */
  1225. // });
  1226.  
  1227. app.media.getRenditionSettings = function( args )
  1228. {
  1229.     var settings;
  1230.  
  1231.     var selection = args.rendition.select( true );
  1232.     if( selection.rendition )
  1233.     {
  1234.         try
  1235.         {
  1236.             // Get playback settings from rendition - throws on failure, never returns null
  1237.             settings = selection.rendition.getPlaySettings( true );
  1238.             settings.players = selection.players;
  1239.             app.media.priv.copyProps( args.settings, settings );  // copy the user's settings
  1240.  
  1241.             return settings;
  1242.         }
  1243.         catch( e )
  1244.         {
  1245.             // FNF or open failure? Rethrow the exception unless we can handle it here
  1246.             if( e.name != "RaiseError" )
  1247.                 throw e;
  1248.  
  1249.             if( e.raiseSystem != app.media.raiseSystem.fileError )
  1250.                 throw e;
  1251.  
  1252.             if( e.raiseCode != app.media.raiseCode.fileNotFound  &&
  1253.                 e.raiseCode != app.media.raiseCode.fileOpenFailed )
  1254.                 throw e;
  1255.  
  1256.             app.media.alert( 'FileNotFound', args, { fileName: selection.rendition.fileName } );
  1257.         }
  1258.     }
  1259.     else  // no rendition in selection
  1260.     {
  1261.         app.media.alert( 'SelectFailed', args, { selection: selection } );
  1262.     }
  1263.  
  1264.     // Did we fail after finding a rendition? If so, use its alt text if allowed
  1265.     return app.media.getAltTextSettings( args, selection );
  1266. }
  1267.  
  1268.  
  1269. // Return the first media rendition in a rendition list, or null if there is no match.
  1270.  
  1271. app.media.getFirstRendition = function( list )
  1272. {
  1273.     for( var i = 0;  i < list.length;  i++ )
  1274.     {
  1275.         if( list[i].rendition.type == app.media.renditionType.media )
  1276.             return list[i].rendition;
  1277.     }
  1278.  
  1279.     return null;
  1280. }
  1281.  
  1282.  
  1283. // Return a settings object with a data property to play a URL.
  1284. // Any properties in args.settings are shallow-copied into the returned settings object.
  1285.  
  1286. // settings = app.media.getURLSettings({
  1287. //     URL: String, /* required */
  1288. //     mimeType: String, /* optional */
  1289. //     settings: MediaSettings /* optional */
  1290. // });
  1291.  
  1292. app.media.getURLSettings = function( args )
  1293. {
  1294.     // Get a data object for the URL and MIME type
  1295.     var settings =
  1296.     {
  1297.         data: app.media.getURLData( args.URL, args.mimeType )
  1298.     }
  1299.  
  1300.     app.media.priv.copyProps( args.settings, settings );  // copy the user's settings
  1301.  
  1302.     return settings;
  1303. }
  1304.  
  1305.  
  1306. // Return an alt text settings object for a selection, or null if there's no alt text available,
  1307. // or if alt text should not be used in this situation.
  1308. // Arguments are the same as app.media.getRenditionSettings().
  1309. // Any properties in args.settings are shallow-copied into the returned settings object.
  1310.  
  1311. app.media.getAltTextSettings = function( args, selection )
  1312. {
  1313.     if( ! args.showAltText )
  1314.         return null;
  1315.  
  1316.     var rendition = selection.rendition || app.media.getFirstRendition( selection.rejects );
  1317.     if( ! rendition )
  1318.         return null;
  1319.  
  1320.     settings = rendition.getPlaySettings( false );
  1321.     app.media.priv.copyProps( args.settings, settings );  // copy the user's settings
  1322.  
  1323.     // Use alt text only when docked (compute default windowType first if needed)
  1324.     if( ! settings.windowType ) 
  1325.         settings.windowType = app.media.priv.computeDefaultWindowType( args, settings );
  1326.     if( settings.windowType != app.media.windowType.docked )
  1327.         return null;
  1328.  
  1329.     // Get the alt text, or default text if none specified and showEmptyAltText is true
  1330.     var text = rendition.altText;
  1331.     if( text.length == 0 )
  1332.     {
  1333.         if( ! args.showEmptyAltText )
  1334.             return null;
  1335.  
  1336.         text = app.media.priv.getString( "IDS_ERROR_NO_ALT_TEXT_SPECIFIED" );
  1337.     }
  1338.  
  1339.     settings.data = app.media.getAltTextData( text ); 
  1340.  
  1341.     settings.players = [ app.media.priv.altTextPlayerID ];
  1342.  
  1343.     return settings;
  1344. }
  1345.  
  1346.  
  1347. // Add the standard event listeners to a player.
  1348. // If annot is specified, set up so when the player is opened, the annot will have its standard
  1349. // event listeners attached. The player.annot and annot.player cross-references will be 
  1350. // installed at the same time.
  1351. // The player must have a settings property. In the settings property, windowType and visible
  1352. // are the only values used here. The visible property may be modified here and restored later
  1353. // in the afterReady listener.
  1354.  
  1355. app.media.addStockEvents = function( player, annot )
  1356. {
  1357.     if( player.stockEvents )
  1358.         return;  // already added stock events
  1359.  
  1360.     app.media.priv.AddStockEventsHelper( player, app.media.getPlayerStockEvents( player.settings ) );
  1361.  
  1362.     if( annot )
  1363.     {
  1364.         // remember that annot needs stock events attached when player is opened
  1365.         player.annot = annot;
  1366.     }
  1367. }
  1368.  
  1369.  
  1370. // Private function to add stock events to an object. Saves a reference to the original stock
  1371. // events in object.stockEvents for later removal. object.stockEvents must not be modified after
  1372. // it is saved here, or removal will not work correctly.
  1373.  
  1374. app.media.priv.AddStockEventsHelper = function( object, events )
  1375. {
  1376.     object.stockEvents = events;
  1377.  
  1378.     if( ! object.events )
  1379.         object.events = new app.media.Events;
  1380.  
  1381.     object.events.add( events )
  1382. }
  1383.  
  1384.  
  1385. // Remove the standard event listeners and cross-references from a player and its associated annot.
  1386. // Does nothing if no stock events (never added or already removed).
  1387.  
  1388. app.media.removeStockEvents = function( player )
  1389. {
  1390.     if( ! player  ||  ! player.stockEvents )
  1391.         return;
  1392.  
  1393.     function removeProps( object )
  1394.     {
  1395.         if( object.events )
  1396.         {
  1397.             object.events.remove( object.stockEvents );
  1398.             delete object.stockEvents;
  1399.         }
  1400.     }
  1401.  
  1402.     removeProps( player );
  1403.  
  1404.     if( player.annot )
  1405.     {
  1406.         if( player.annot.stockEvents )
  1407.             removeProps( player.annot );
  1408.  
  1409.         delete player.annot.player;
  1410.         delete player.annot;
  1411.     }
  1412. }
  1413.  
  1414.  
  1415. // Return floating window rect for a doc, floating params, monitor to play on, and
  1416. // optional array containing the dimensions [l,r,t,b] of any additional controller UI.
  1417.  
  1418. // NOTE: this method is called from both JS and C++ code, so do not change its signature 
  1419. // without great care!
  1420.  
  1421. app.media.computeFloatWinRect = function( doc, floating, whichMonitor, uiSize )
  1422. {
  1423.     // Figure out rect in virtual desktop space that we are positioning relative to
  1424.     var overRect;
  1425.     switch( floating.over )
  1426.     {
  1427.         default:
  1428.         case app.media.over.pageWindow:
  1429.             overRect = doc.pageWindowRect;
  1430.             break;
  1431.  
  1432.         case app.media.over.appWindow:
  1433.             // Inner more consistent placement because no borders etc.
  1434.             overRect = doc.innerAppWindowRect;
  1435.             break;
  1436.  
  1437.         case app.media.over.desktop:
  1438.             overRect = app.monitors.desktop()[0].rect;
  1439.             break;
  1440.  
  1441.         case app.media.over.monitor:
  1442.             overRect = app.monitors.select( whichMonitor, doc )[0].workRect;
  1443.             break;
  1444.     }
  1445.  
  1446.     // Get the border sizes for this window
  1447.     var border = app.media.getWindowBorderSize( floating );
  1448.  
  1449.     // Align floating window with overRect according to align, using the
  1450.     // floating window rect plus the border sizes
  1451.     rect = app.media.priv.rectAlign(
  1452.         overRect, floating.align, 
  1453.         floating.width  + border[0] + border[2],
  1454.         floating.height + border[1] + border[3] );
  1455.  
  1456.     // Grow the rect by the UI size (if any)
  1457.     if( uiSize )
  1458.         rect = app.media.priv.rectGrow( rect, uiSize );
  1459.  
  1460.     return rect;
  1461. }
  1462.  
  1463.  
  1464. // Return a new instance of the standard player events for the given settings.
  1465. // In the settings property, windowType and visible are the only values used here.
  1466. // The settings.visible property may be modified here, and restored later in an afterReady event.
  1467. // If you call this method directly and there is an annot associated with the player, you must
  1468. // set player.annot and annot.player as shown in addStockEvents().
  1469.  
  1470. app.media.getPlayerStockEvents = function( settings )
  1471. {
  1472.     var events = new app.media.Events;
  1473.  
  1474.     if( app.media.trace )
  1475.         events.add( app.media.getPlayerTraceEvents() );
  1476.  
  1477.     events.add(
  1478.     {
  1479.         onClose: function( e )
  1480.         {
  1481.             var annot = e.target.annot;
  1482.  
  1483.             app.media.removeStockEvents( e.target );    // must do this before setFocus call below
  1484.  
  1485.             if( annot )
  1486.             {
  1487.                 annot.extFocusRect = null;
  1488.  
  1489.                 // If docked screen had focus when closed, and further playback is allowed,
  1490.                 // put focus back on annot
  1491.                 if( e.media.hadFocus  &&
  1492.                     e.target.settings.windowType == app.media.windowType.docked  &&
  1493.                     e.media.doc.media.canPlay.yes )
  1494.                 {
  1495.                     // Allow async setFocus since we're in event method
  1496.                     // Does not fire stock annot Focus event because stock events removed above
  1497.                     annot.setFocus( true );    
  1498.                 }
  1499.             }
  1500.         },
  1501.  
  1502.         afterDone: function( e )
  1503.         {
  1504.             e.target.close( app.media.closeReason.done );  // fires Close and may fire Blur
  1505.         },
  1506.  
  1507.         afterError: function( e )
  1508.         {
  1509.             app.media.alert( 'PlayerError', e.target.args, { errorText: e.media.text } );
  1510.             e.target.close( app.media.closeReason.error );  // fires Close and may fire Blur
  1511.         },
  1512.  
  1513.         afterEscape: function( e )
  1514.         {
  1515.             e.target.close( app.media.closeReason.uiScreen );  // fires Close and may fire Blur
  1516.         }
  1517.     });
  1518.  
  1519.     // Add player event listeners for specific window types
  1520.     switch( settings.windowType )
  1521.     {
  1522.         case app.media.windowType.docked:
  1523.         {
  1524.             events.add(
  1525.             {
  1526.                 onGetRect: function( e )
  1527.                 {
  1528.                     if( e.target.annot )
  1529.                     {
  1530.                         // Get the annot's rectangle and expand it to include any
  1531.                         // visible media player user interface. Return this rectangle in
  1532.                         // the event object, and also use it as the annot's focus rect.
  1533.                         e.target.annot.extFocusRect = e.media.rect =
  1534.                             app.media.priv.rectGrow(
  1535.                                 e.target.annot.innerDeviceRect, e.target.uiSize );
  1536.                     }
  1537.                 },
  1538.  
  1539.                 onBlur: function( e )
  1540.                 {
  1541.                     if( e.target.annot )
  1542.                         e.target.annot.alwaysShowFocus = false;    
  1543.                 },
  1544.  
  1545.                 onFocus: function( e )
  1546.                 {
  1547.                     if( e.target.annot )
  1548.                         e.target.annot.alwaysShowFocus = true;
  1549.                 }
  1550.             });
  1551.         }
  1552.         break;
  1553.  
  1554.         case app.media.windowType.floating:
  1555.         {
  1556.             // Need either a rect or a width and height
  1557.             if ( !settings.floating.rect && ( !settings.floating.width || !settings.floating.height ) )
  1558.                 app.media.priv.throwBadArgs();    // throw exception
  1559.  
  1560.             if( settings.visible === undefined )
  1561.                 settings.visible = app.media.defaultVisible;
  1562.  
  1563.             if( settings.visible )
  1564.             {
  1565.                 // Hide floating window while it's being created, then show it after the
  1566.                 // controller dimensions are available
  1567.                 settings.visible = false;
  1568.  
  1569.                 events.add(
  1570.                 {
  1571.                     afterReady: function( e )
  1572.                     {
  1573.                         var floating = e.target.settings.floating;
  1574.                         var rect = floating.rect;  // take user-provided rect, or calculate one
  1575.                         if( ! rect )
  1576.                         {
  1577.                             rect = app.media.computeFloatWinRect( e.media.doc, floating,
  1578.                                 e.target.settings.monitorType, e.target.uiSize );
  1579.                         }
  1580.                         else
  1581.                         {
  1582.                             // Grow passed rect by UI size
  1583.                             rect = app.media.priv.rectGrow( rect, e.target.uiSize );
  1584.                         }
  1585.  
  1586.                         // Are we supposed to move the window onscreen if it is offscreen?
  1587.                         if( floating.ifOffScreen == app.media.ifOffScreen.forceOnScreen )
  1588.                         {
  1589.                             // Make sure window rect is totally onscreen, NOP if onscreen already                                                                                   
  1590.                             rect = app.media.constrainRectToScreen( rect,
  1591.                                 app.media.priv.rectAnchorPt( rect, floating.align ) );
  1592.                         }
  1593.  
  1594.                         // Set the outer rect
  1595.                         e.target.outerRect = rect;
  1596.  
  1597.                         // Show the window and give it the focus
  1598.                         e.target.visible = true;
  1599.                         e.target.setFocus();    // fires Focus event
  1600.                     }
  1601.                 });
  1602.             }
  1603.         }
  1604.         break;
  1605.     }
  1606.  
  1607.     return events;
  1608. }
  1609.  
  1610.  
  1611. // Return a new instance of the debug trace event listeners for a player.
  1612.  
  1613. app.media.getPlayerTraceEvents = function()
  1614. {
  1615.     return new app.media.Events(
  1616.     {
  1617.         onEveryEvent: function( e )
  1618.         {
  1619.             if( e.media.id != 'GetRect' )  // cannot trace inside onGetRect, it can hang Acrobat
  1620.                 app.media.priv.trace( 'player event: on' + e.media.id );
  1621.         },
  1622.  
  1623.         afterEveryEvent: function( e )
  1624.         {
  1625.             app.media.priv.trace( 'player event: after' + e.media.id );
  1626.         },
  1627.  
  1628.         onScript: function( e )
  1629.         {
  1630.             app.media.priv.trace( "player onScript('" + e.media.command + "','" + e.media.param + "')" );
  1631.         },
  1632.  
  1633.         afterScript: function( e )
  1634.         {
  1635.             app.media.priv.trace( "player afterScript('" + e.media.command + "','" + e.media.param + "')" );
  1636.         },
  1637.  
  1638.         onStatus: function( e )
  1639.         {
  1640.             app.media.priv.trace( "player onStatus: " +
  1641.                 ( e.media.progress >= 0 ? e.media.progress + "/" + e.media.total + ", " : "" ) +
  1642.                 "  status code: " + e.media.code + ": '" + e.media.text + "'" );
  1643.         },
  1644.  
  1645.         afterStatus: function( e )
  1646.         {
  1647.             app.media.priv.trace( "player afterStatus: " +
  1648.                 ( e.media.progress >= 0 ? e.media.progress + "/" + e.media.total + ", " : "" ) +
  1649.                 "  status code: " + e.media.code + ": '" + e.media.text + "'" );
  1650.         }
  1651.     });
  1652. }
  1653.  
  1654.  
  1655. // Return a new instance of the standard annot events:
  1656. // For a docked player, handle Focus and Blur to give the player the focus instead of the annot.
  1657. // For any type of player, close the player on Destroy.
  1658.  
  1659. app.media.getAnnotStockEvents = function( windowType )
  1660. {
  1661.     var events = new app.media.Events;
  1662.  
  1663.     if( app.media.trace )
  1664.         events.add( app.media.getAnnotTraceEvents() );
  1665.  
  1666.     events.add(
  1667.     {
  1668.         onDestroy: function( e )
  1669.         {
  1670.             if( e.target.player ) 
  1671.             { 
  1672.                 // NOP if not open
  1673.                 // fires Close and possibly other events
  1674.                 e.target.player.close( app.media.closeReason.docChange );    
  1675.             }
  1676.         },
  1677.     } );
  1678.  
  1679.     if( windowType == app.media.windowType.docked ) 
  1680.     {
  1681.         events.add(
  1682.         {
  1683.             onFocus: function( e )
  1684.             {
  1685.                 // If player is open, give it the focus. This event could be fired while doing
  1686.                 // UI inside player.open() or the like.
  1687.                 if( e.target.player.isOpen )
  1688.                 {
  1689.                     e.target.player.setFocus();  // fires Focus for player and Blur for annot
  1690.                 }
  1691.  
  1692.                 // Prevent any action from being fired for the Focus event, since focus
  1693.                 // has already been removed inside setFocus(). If setFocus() not called,
  1694.                 // we're in the process of some sort of UI and we don't want random actions
  1695.                 // firing either.
  1696.                 e.stopDispatch = true;
  1697.             },
  1698.  
  1699.             onBlur: function( e )
  1700.             {
  1701.                 // As with the Focus event, prevent any action from being fired for the Blur event.
  1702.                 // This also prevents anybody after us from seeing onBlur before onFocus because
  1703.                 // of our setFocus() call within onFocus.
  1704.                 e.stopDispatch = true;
  1705.             }
  1706.         });
  1707.     }
  1708.  
  1709.     return events;
  1710. }
  1711.  
  1712.  
  1713. // Return a new instance of the debug trace event listeners for an annot.
  1714.  
  1715. app.media.getAnnotTraceEvents = function()
  1716. {
  1717.     return new app.media.Events(
  1718.     {
  1719.         onEveryEvent: function( e )
  1720.         {
  1721.             app.media.priv.trace( 'annot event: on' + e.media.id );
  1722.         },
  1723.  
  1724.         afterEveryEvent: function( e )
  1725.         {
  1726.             app.media.priv.trace( 'annot event: after' + e.media.id );
  1727.         }
  1728.     });
  1729. }
  1730.  
  1731.  
  1732. // Make a shallow copy of args and run our "Do What I Mean" logic on it, to fill in default values
  1733. // used in app.media.createPlayer(). The original args object is not modified, and changes made
  1734. // later to the copy do not affect the original. Objects inside args are shared between the
  1735. // original and the copy, and changes made inside these objects are visible from both args objects.
  1736.  
  1737. // If args.annot or args.rendition are not defined, gets them from current event object.
  1738. // If args.doc is not defined, gets it from args.annot or args.rendition.
  1739. // Throws exception on failure.
  1740.  
  1741. app.media.argsDWIM = function( args )
  1742. {
  1743.     if( args && args.privDWIM )
  1744.         return args;  // already did a DWIM copy
  1745.  
  1746.     args = app.media.priv.copyProps( args );
  1747.     args.privDWIM = true;
  1748.  
  1749.     // Use annot and rendition passed in parameters, or get them from event object
  1750.     if( event && event.action )
  1751.     {
  1752.         if( ! args.annot ) 
  1753.             args.annot = event.action.annot;    // TODO: it'd be nice to verify type of annot here...
  1754.  
  1755.         if( ! args.rendition )
  1756.             args.rendition = event.action.rendition;
  1757.     }
  1758.  
  1759.     // Get doc from rendition or annot if args.doc not provided
  1760.     if( ! args.doc )
  1761.     {
  1762.             if( args.rendition && args.annot )
  1763.                 if( args.rendition.doc != args.annot.doc )
  1764.                     app.media.priv.throwBadArgs();
  1765.     
  1766.             if( args.rendition )
  1767.                 args.doc = args.rendition.doc;
  1768.             else if( args.annot )
  1769.                 args.doc = args.annot.doc;
  1770.         }
  1771.  
  1772.     // If fromUser is not specified, use !! to set it to true or false based on event name
  1773.     if( args.fromUser === undefined )
  1774.         args.fromUser = !!( event && event.name && ! app.media.pageEventNames[event.name] );
  1775.  
  1776.     if( args.showAltText === undefined )
  1777.         args.showAltText = true;
  1778.  
  1779.     if( args.showEmptyAltText === undefined )
  1780.         args.showEmptyAltText = ! args.fromUser;
  1781.  
  1782.     return args;
  1783. }
  1784.  
  1785.  
  1786. // Private function for app.media.priv.createPlayer().
  1787.  
  1788. app.media.priv.createPlayer = function( args )
  1789. {
  1790.     app.media.priv.trace( "app.media.priv.createPlayer" );
  1791.  
  1792.     if( ! args.doc )
  1793.         app.media.priv.throwBadArgs();    // doc is required
  1794.  
  1795.     if( ! app.media.canPlayOrAlert( args ) )
  1796.         return null;  // playback is not allowed, user has been notified
  1797.  
  1798.     if( args.annot && args.annot.player )
  1799.     {
  1800.         args.annot.player.close( app.media.closeReason.play );    // fires events
  1801.         // args.annot.player presumably is null now, unless onClose didn't null it out.
  1802.         // Cannot create new player in onClose so shouldn't have any issues there
  1803.     }
  1804.  
  1805.     var player = args.doc.media.newPlayer({ args: args });
  1806.  
  1807.     // Get a settings object for either a URL or a rendition, whichever was provided
  1808.     // URL wins if both present.
  1809.     player.settings =
  1810.         args.URL ? app.media.getURLSettings( args ) :
  1811.         args.rendition ? app.media.getRenditionSettings( args ) :
  1812.         app.media.priv.throwBadArgs();  // need either rendition or URL
  1813.  
  1814.     if( ! player.settings )
  1815.         return null;  // user has been notified already
  1816.  
  1817.     // If no windowType, compute default value
  1818.     if( ! player.settings.windowType ) 
  1819.         player.settings.windowType = app.media.priv.computeDefaultWindowType( args, player.settings );
  1820.     
  1821.     // If windowType couldn't be computed, throw
  1822.     if( ! player.settings.windowType ) 
  1823.         app.media.priv.throwBadArgs(); 
  1824.  
  1825.     switch( player.settings.windowType )
  1826.     {
  1827.         case app.media.windowType.docked:
  1828.         {
  1829.             if( player.settings.page === undefined )
  1830.             {
  1831.                 if( ! args.annot )
  1832.                     app.media.priv.throwBadArgs();  // need either an annot or a page number
  1833.  
  1834.                 player.settings.page = args.annot.page;
  1835.             }
  1836.         }
  1837.         break;
  1838.  
  1839.         case app.media.windowType.fullScreen:
  1840.         {
  1841.             player.settings.monitor = app.monitors.select( player.settings.monitorType, args.doc );
  1842.         }
  1843.         break;
  1844.     }
  1845.  
  1846.     // Add any stock events to the player (and set up to add them to the annot later if needed). 
  1847.     // Even if the player is never opened, no need to remove them since they won't get used.
  1848.     if( ! args.noStockEvents )
  1849.         app.media.addStockEvents( player, args.annot );
  1850.  
  1851.     if( args.events )
  1852.     {
  1853.         if( ! player.events )
  1854.             player.events = new app.media.Events;
  1855.  
  1856.         player.events.add( args.events );  // Add caller's custom events
  1857.     }
  1858.         
  1859.     return player;
  1860. }
  1861.  
  1862.  
  1863. // Private function to get default windowType:
  1864. // docked if there is an annot,
  1865. // floating if there is no annot and there is a floating settings obj,
  1866. // undefined otherwise
  1867.  
  1868. app.media.priv.computeDefaultWindowType = function( args, settings )
  1869. {
  1870.     var retWT;
  1871.     if( args.annot )
  1872.         retWT = app.media.windowType.docked;
  1873.     else if( settings.floating )
  1874.         retWT = app.media.windowType.floating;
  1875.  
  1876.     return retWT;
  1877. }
  1878.  
  1879. // Display an alert, given an alert name (e.g. FileNotFound), createArgs object (the type of object
  1880. // processed by app.media.argsDWIM()), and optional specificArgs object which contains information 
  1881. // specific to the alert. The only property that MUST be present in createArgs is doc.
  1882. // NOTE: This implementation of app.media.alert is for private use within media.js only.
  1883. // Do not use it in PDF files! It will be changed in a future release.
  1884.  
  1885. app.media.alert = function( name, createArgs, specificArgs )
  1886. {
  1887.     var alertArgs = { name: name, createArgs: createArgs, alerterData: {} }
  1888.     app.media.priv.copyProps( specificArgs, alertArgs );
  1889.  
  1890.     // Set the default doc alerts if they are not already set
  1891.     if( !( 'alerts' in createArgs.doc.media ) )
  1892.         createArgs.doc.media.alerts = new app.media.Alerts;
  1893.  
  1894.     // If there are no alerts, use the doc alerts
  1895.     var curAlerts = createArgs.alerts;
  1896.     if ( !curAlerts )
  1897.         curAlerts = createArgs.doc.media.alerts; 
  1898.  
  1899.     // Keep on dispatching up parent chain of alerters until finally handled or no more alerters
  1900.     var gpMN = 'getParent';
  1901.     var handled = false;
  1902.     var lastAlerts = null;
  1903.     while ( curAlerts && !handled )
  1904.     {
  1905.         lastAlerts = curAlerts;
  1906.  
  1907.         // dispatch
  1908.         handled = dispatchAlert( curAlerts );
  1909.         if ( !handled ) 
  1910.         {
  1911.             // not handled, try to move to parent alerters
  1912.             var newAlerts = null;
  1913.             if ( gpMN in curAlerts )
  1914.             {
  1915.                 newAlerts = curAlerts[ gpMN ]( alertArgs ); 
  1916.                 if ( newAlerts ) 
  1917.                 {
  1918.                     // setup so parent alter can see child's alertArgs if it wants to
  1919.                     // and to give reference to child alerter
  1920.                     var newCI = {};
  1921.                     newCI.alerts = curAlerts;
  1922.                     newCI.alerterData = alertArgs.alerterData;
  1923.                     newCI.next = alertArgs.childInfo;
  1924.                     alertArgs.childInfo = newCI;
  1925.  
  1926.                     // Clear stuff out so new alerter gets fresh shot
  1927.                     alertArgs.alerterData = {};
  1928.                     delete alertArgs.stop;
  1929.                     delete alertArgs.sendToParent;
  1930.                 }
  1931.             }
  1932.                     
  1933.             curAlerts = newAlerts;
  1934.         }
  1935.     }
  1936.     // In inverse order, call afterParent as needed (not for top of course)
  1937.     curAlerts = lastAlerts;
  1938.     while ( alertArgs.childInfo ) {
  1939.  
  1940.         var newPI = {};
  1941.         newPI.alerts = curAlerts;
  1942.         newPI.alerterData = alertArgs.alerterData;
  1943.         newPI.next = alertArgs.parentInfo;
  1944.         alertArgs.parentInfo = newPI;
  1945.  
  1946.         alertArgs.alerterData = alertArgs.childInfo.alerterData;
  1947.         curAlerts = alertArgs.childInfo.alerts;
  1948.         alertArgs.childInfo = alertArgs.childInfo.next;
  1949.  
  1950.         var method = curAlerts[ 'afterParent' ];
  1951.         if( typeof method == 'function' )
  1952.             method.call( curAlerts, alertArgs, handled );
  1953.     }
  1954.  
  1955.  
  1956.  
  1957.     // Local function to dispatch to an alerts object and return true if handled
  1958.     function dispatchAlert( alerts )
  1959.     {
  1960.         var other = 'alertOther';
  1961.         var alertMtdName = 'alert' + name;
  1962.  
  1963.         if( alertMtdName in alerts || other in alerts )
  1964.         {
  1965.             callMethod( 'before' );
  1966.  
  1967.             if ( ! callMethod( alertMtdName ) )
  1968.                 callMethod( other );
  1969.  
  1970.             callMethod( 'after' );
  1971.  
  1972.             return !alertArgs.sendToParent;
  1973.         }
  1974.  
  1975.         // Local function to call a single alerts method and return true if it is present
  1976.         function callMethod( methodName )
  1977.         {
  1978.             if( methodName in alerts  &&  ! alertArgs.stop  &&  !alertArgs.sendToParent )
  1979.             {
  1980.                 var method = alerts[methodName];
  1981.                 if( typeof method == 'function' )
  1982.                     method.call( alerts, alertArgs );
  1983.  
  1984.                 return true;
  1985.             }
  1986.         }
  1987.  
  1988.         return false;
  1989.     }
  1990. }
  1991.  
  1992.  
  1993. // app.media.Alerts - constructs a stock alerts object
  1994. // If an alert is the result of a user-triggered event, always show alert with no checkbox.
  1995. // If not user triggered, we include a "don't show again" checkbox and save its state,
  1996. // but don't show the alert if the user has previously checked that box in this document.
  1997. // The "don't show again" state is shared by all alerts (not tracked for each different type).
  1998. // NOTE: This implementation of app.media.Alerts is for private use within media.js only.
  1999. // Do not use it in PDF files! It will be changed in a future release.
  2000.  
  2001. app.media.Alerts = function()
  2002. {
  2003.     return {
  2004.  
  2005.         before: function( alertArgs )
  2006.         {
  2007.             if( ! alertArgs.createArgs.fromUser  &&  this.skip )
  2008.                 alertArgs.stop = true;
  2009.         },
  2010.  
  2011.         after: function( alertArgs )
  2012.         {
  2013.             if( ! alertArgs.createArgs.fromUser )
  2014.                 this.skip = alertArgs.alerterData.skip;
  2015.         },
  2016.  
  2017.         alertCannotPlay: function( alertArgs )
  2018.         {
  2019.             var canPlay = alertArgs.canPlayResult;
  2020.             if( ! canPlay.canShowUI )
  2021.                 alertArgs.stop = true;  // Playback not allowed and may not show UI -- suppress after method
  2022.             else 
  2023.             {
  2024.                 if( canPlay.no.authoring )
  2025.                 {
  2026.                     // Playback is not allowed while authoring
  2027.                     alertArgs.alerterData.skip = this.privOK( alertArgs.createArgs, "IDS_PLAYBACK_DISALLOWED_WHILE_AUTHORING" );
  2028.                 }
  2029.                 else if( canPlay.no.security )
  2030.                 {
  2031.                     // User prefs say "no multimedia"
  2032.                     alertArgs.alerterData.skip = this.privOK( alertArgs.createArgs, "IDS_PLAYBACK_DISALLOWED_CONFIGURATION" );
  2033.                 }
  2034.                 else 
  2035.                 {
  2036.                     // as of now, will not get here -- should probably put up generic error though just in case...
  2037.                 }
  2038.             }
  2039.         },
  2040.  
  2041.         alertException: function( alertArgs )
  2042.         {
  2043.             // No don't show again checkbox for exceptions 
  2044.             app.alert( alertArgs.error.message );
  2045.         },
  2046.  
  2047.         alertFileNotFound: function( alertArgs )
  2048.         {
  2049.             alertArgs.alerterData.skip = app.media.alertFileNotFound( alertArgs.createArgs.doc, alertArgs.fileName, ! alertArgs.createArgs.fromUser );
  2050.         },
  2051.  
  2052.         alertOpen: function( alertArgs )
  2053.         {
  2054.             // TODO - show UI here, except if already shown (e.g. for failPlayerSecurityPrompt)
  2055.             // may want more info from open (e.g. to tell us if UI was shown already)
  2056.         },
  2057.  
  2058.         alertPlayerError: function( alertArgs )
  2059.         {
  2060.             alertArgs.alerterData.skip = this.privOK( alertArgs.createArgs, "IDS_JS_PLAYBACK_ERROR", alertArgs.errorText );
  2061.         },
  2062.  
  2063.         alertSelectFailed: function( alertArgs )
  2064.         {
  2065.             alertArgs.alerterData.skip = app.media.alertSelectFailed(
  2066.                 alertArgs.createArgs.doc, alertArgs.selection.rejects, ! alertArgs.createArgs.fromUser, alertArgs.createArgs.fromUser );
  2067.         },
  2068.  
  2069.         alertOther: null,  // ignore any other alerts
  2070.  
  2071.         // Display a simple "OK" alert with optional "don't show again" checkbox.
  2072.         // Return checkbox result value, or undefined if no checkbox.
  2073.         privOK: function( createArgs, idMsg, strAppend )
  2074.         {
  2075.             var a = { cMsg: app.media.priv.getString(idMsg) + ( strAppend || '' ),
  2076.                       nIcon: 0, nType: 0, oDoc: createArgs.doc };
  2077.  
  2078.             if( ! createArgs.fromUser )
  2079.                 a.oCheckbox = { cMsg: app.media.priv.getString("IDS_DONOT_SHOW_AGAIN_DOC"),
  2080.                                 bInitialValue: false };
  2081.  
  2082.             app.alert( a );
  2083.  
  2084.             if( a.oCheckbox )
  2085.                 return a.oCheckbox.bAfterValue;
  2086.         },
  2087.     }
  2088. }
  2089.  
  2090.  
  2091. // Debugging code
  2092.  
  2093. app.media.priv.dumpObject = function( obj, str, bValues )
  2094. {
  2095.     if( ! str )
  2096.         str = "";
  2097.     else
  2098.         str += " ";
  2099.  
  2100.     str += "(" + obj + ") [" + typeof(obj) + "]\n";
  2101.  
  2102.     for( var prop in obj )
  2103.         str += "   " + prop + ( bValues ? ": " + obj[prop] : "" ) + "\n";
  2104.  
  2105.     app.media.priv.trace( str );
  2106. }
  2107.  
  2108.  
  2109. app.media.priv.dumpNames = function( obj, str )
  2110. {
  2111.     app.media.priv.dumpObject( obj, str, false );
  2112. }
  2113.  
  2114.  
  2115. app.media.priv.dumpValues = function( obj, str )
  2116. {
  2117.     app.media.priv.dumpObject( obj, str, true );
  2118. }
  2119.  
  2120.  
  2121. app.media.priv.dumpArray = function( array, str )
  2122. {
  2123.     if( ! str )
  2124.         str = "";
  2125.     else
  2126.         str += " ";
  2127.  
  2128.     str += "(" + array + ") [" + typeof(array) + "]\n{ ";
  2129.  
  2130. /*
  2131.     if( array.length )
  2132.         app.alert( "has length" );
  2133.     else
  2134.         app.alert( "no length" );
  2135. */
  2136.  
  2137.     for( var i = 0;  i < array.length;  i++ )
  2138.         str += array[i] + ( i < array.length - 1 ? ", " : " }" );
  2139.  
  2140.     app.media.priv.trace( str );
  2141. }
  2142.  
  2143.  
  2144. app.media.priv.trace = function( str )
  2145. {
  2146.     if( app.media.trace )
  2147.         console.println( str );
  2148. }
  2149.  
  2150.  
  2151. // Private function called in a rendition action. Puts up UI on failure.
  2152.  
  2153. app.media.priv.stopAnnotPlayer = function()
  2154. {
  2155.     try
  2156.     {
  2157.         annot = event.action.annot;
  2158.         if( annot.player )
  2159.             annot.player.close( app.media.closeReason.stop );    // fires Close event, may fire Blur
  2160.     }
  2161.     catch( e )
  2162.     {
  2163.         app.alert( e.message );
  2164.     }
  2165. }
  2166.  
  2167.  
  2168. // Private function called in a rendition action. NOP if already paused. Puts up UI on failure.
  2169.  
  2170. app.media.priv.pauseAnnotPlayer = function()
  2171. {
  2172.     try
  2173.     {
  2174.         annot = event.action.annot;
  2175.         if( annot.player )
  2176.             annot.player.pause();
  2177.     }
  2178.     catch( e )
  2179.     {
  2180.         app.alert( e.message );
  2181.     }
  2182. }
  2183.  
  2184.  
  2185. // Private function called in a rendition action. NOP if not paused. Puts up UI on failure.
  2186.  
  2187. app.media.priv.resumeAnnotPlayer = function()
  2188. {
  2189.     try
  2190.     {
  2191.         annot = event.action.annot;
  2192.         if( annot.player )
  2193.             annot.player.play();
  2194.     }
  2195.     catch( e )
  2196.     {
  2197.         app.alert( e.message );
  2198.     }
  2199. }
  2200.  
  2201.  
  2202. // Enumerate and copy properties from one object to another object or to a new object, and
  2203. // return the resulting object. This is a shallow copy: any objects referenced by the from
  2204. // object will now be referenced by both the from and to objects. 
  2205.  
  2206. app.media.priv.copyProps = function( from, to )
  2207. {
  2208.     if( ! to )
  2209.         to = {};
  2210.  
  2211.     if( from ) 
  2212.     {
  2213.         for( var name in from )
  2214.             to[name] = from[name];
  2215.     }
  2216.  
  2217.     return to;
  2218. }
  2219.  
  2220.  
  2221. // Rectangle utility functions
  2222.  
  2223.  
  2224. // Tables used  by rectAlign to map app.media.align values to window positioning multipliers.
  2225.  
  2226. // see app.media.align.*       un    tl    tc    tr    cl    c     cr    bl    bc    br
  2227. app.media.priv.xPosTable =  [ 0.5,  0.0,  0.5,  1.0,  0.0,  0.5,  1.0,  0.0,  0.5,  1.0 ];
  2228. app.media.priv.yPosTable =  [ 0.5,  0.0,  0.0,  0.0,  0.5,  0.5,  0.5,  1.0,  1.0,  1.0 ];
  2229.  
  2230.  
  2231. // Given an app.media.align value and a desired width and height, return a rectangle
  2232. // aligned with rect according to the align value.
  2233.  
  2234. app.media.priv.rectAlign = function( rect, align, width, height )
  2235. {
  2236.     if( ! align )
  2237.         align = app.media.align.center;
  2238.  
  2239.     var x = rect[0] + ( rect[2] - rect[0] - width  ) * app.media.priv.xPosTable[align];
  2240.     var y = rect[1] + ( rect[3] - rect[1] - height ) * app.media.priv.yPosTable[align];
  2241.  
  2242.     return [ x, y, x + width, y + height ];
  2243. }
  2244.  
  2245.  
  2246. // Given an app.media.align value and a rect, return an anchor point
  2247.  
  2248. app.media.priv.rectAnchorPt = function( rect, align )
  2249. {
  2250.     if( ! align )
  2251.         align = app.media.align.center;
  2252.  
  2253.     var x = rect[0] + ( ( rect[2] - rect[0] ) * app.media.priv.xPosTable[align] );
  2254.     var y = rect[1] + ( ( rect[3] - rect[1] ) * app.media.priv.yPosTable[align] );
  2255.  
  2256.     return [ x, y ];
  2257. }
  2258.  
  2259.  
  2260. // Returns the area of a rectangle.  If rect is empty, 0 is returned.
  2261.  
  2262. app.media.priv.rectArea = function( rect )
  2263. {
  2264.     if( app.media.priv.rectIsEmpty( rect ) )    // empty rect might be [10,10,0,0] so cannot just do the math
  2265.         return 0;
  2266.     else
  2267.         return ( rect[2] - rect[0] ) * ( rect[3] - rect[1] );
  2268. }
  2269.  
  2270.  
  2271. // Returns rect grown by size, an array of four values giving the amount to grow each edge.
  2272. // Returned rect is a new object, input rect is not modified.
  2273.  
  2274. app.media.priv.rectGrow = function( rect, size )
  2275. {
  2276.     return [
  2277.         rect[0] - size[0],
  2278.         rect[1] - size[1],
  2279.         rect[2] + size[2],
  2280.         rect[3] + size[3]
  2281.     ];
  2282. }
  2283.  
  2284.  
  2285. // Returns the intersection of two rectangles.
  2286. // If either input rect is empty, or there is no intersection, [0,0,0,0] is returned.
  2287. // Returned rect is a new object, input rects are not modified.
  2288.  
  2289. app.media.priv.rectIntersect = function( rectA, rectB )
  2290. {
  2291.     var newRect;
  2292.  
  2293.     if( app.media.priv.rectIsEmpty(rectA) || app.media.priv.rectIsEmpty(rectB) )
  2294.     {
  2295.         newRect = [ 0, 0, 0, 0 ];
  2296.     }
  2297.     else
  2298.     {
  2299.         newRect =
  2300.         [
  2301.             Math.max( rectA[0], rectB[0] ),
  2302.             Math.max( rectA[1], rectB[1] ),
  2303.             Math.min( rectA[2], rectB[2] ),
  2304.             Math.min( rectA[3], rectB[3] )
  2305.         ];
  2306.  
  2307.         if( app.media.priv.rectIsEmpty( newRect ) )
  2308.             newRect = [ 0, 0, 0, 0 ];
  2309.     }
  2310.  
  2311.     return newRect;
  2312. }
  2313.  
  2314.  
  2315. // Take the intersection of two rectangles and return its area.
  2316.  
  2317. app.media.priv.rectIntersectArea = function( rectA, rectB )
  2318. {
  2319.     return app.media.priv.rectArea( app.media.priv.rectIntersect( rectA, rectB ) );
  2320. }
  2321.  
  2322.  
  2323. // Is a rectangle empty? 
  2324.  
  2325. app.media.priv.rectIsEmpty = function( rect )
  2326. {
  2327.     return  ! rect  ||  rect[0] >= rect[2]  ||  rect[1] >= rect[3];
  2328. }
  2329.  
  2330.  
  2331. // Returns new object that contains same values (not custom values) as input rect.
  2332.  
  2333. app.media.priv.rectCopy = function( rect )
  2334. {
  2335.     return [ rect[0], rect[1], rect[2], rect[3] ];
  2336. }
  2337.  
  2338.  
  2339. // Returns the union of two rectangles.
  2340. // Returned rect is a new object, input rects are not modified.
  2341.  
  2342. app.media.priv.rectUnion = function( rectA, rectB )
  2343. {
  2344.     return(
  2345.         app.media.priv.rectIsEmpty(rectA) ? app.media.priv.rectCopy( rectB ) :
  2346.         app.media.priv.rectIsEmpty(rectB) ? app.media.priv.rectCopy( rectA ) :
  2347.         [
  2348.             Math.min( rectA[0], rectB[0] ),
  2349.             Math.min( rectA[1], rectB[1] ),
  2350.             Math.max( rectA[2], rectB[2] ),
  2351.             Math.max( rectA[3], rectB[3] )
  2352.         ]
  2353.     );
  2354. }
  2355.  
  2356.  
  2357. // Get a resource string
  2358.  
  2359. app.media.priv.getString = function( idString )
  2360. {
  2361.     return app.getString( 'Multimedia', idString );
  2362. }
  2363.  
  2364.  
  2365. // Return a value or a default if the value is undefined
  2366.  
  2367. app.media.priv.valueOr = function( value, def )
  2368. {
  2369.     return value !== undefined ? value : def;
  2370. }
  2371.  
  2372.  
  2373. // Private constants
  2374.  
  2375. app.media.priv.altTextPlayerID = 'vnd.adobe.swname:ADBE_AltText';
  2376.  
  2377.  
  2378. // End of media.js
  2379.  
  2380.  
  2381.